#include "wxscoder.h"
#include <manager.h>
#include <editormanager.h>
#include <configmanager.h>
#include <logmanager.h>
#include <filemanager.h>
#include <encodingdetector.h>
#include <globals.h>
#include <wx/file.h>
#include <wx/intl.h>
#include "M_Code_StyledTextCtrl.h"
#include <editormanager.h>
#include <projectmanager.h>
#include <pluginmanager.h>
#include "sdk_events.h"

static wxsCoder SingletonObject;
wxsCoder* wxsCoder::Singleton = &SingletonObject;

wxsCoder::wxsCoder() {
  FlushTimer.SetOwner( this, 1 );
  Connect( wxEVT_TIMER, ( wxObjectEventFunction )&wxsCoder::FlushTimerEvent );
}

wxsCoder::~wxsCoder() {
  FlushAll();
}

void wxsCoder::AddCode( const wxString& FileName, const wxString& Header, const wxString& End, const wxString& Code, bool Immediately, bool CodeHasHeader, bool CodeHasEnd ) {
  wxMutexLocker Lock( DataMutex );
  wxString FixedFileName = NormalizeFileName( FileName );
  if( FixedFileName.IsEmpty() ) {
    return;
  }
  int Index = CodeChangesFiles.Index( FileName );
  if( Index == wxNOT_FOUND ) {
    Index = CodeChangesFiles.Count();
    CodeChangesFiles.Add( FileName );
    CodeChanges.Add( 0 );
  }
  CodeChange* Change = new CodeChange;
  Change->Header = Header;
  Change->End = End;
  Change->Code = Code;
  Change->CodeHasHeader = CodeHasHeader;
  Change->CodeHasEnd = CodeHasEnd;
  Change->Next = CodeChanges[Index];
  CodeChanges[Index] = Change;
  for( CodeChange *Prev = Change, *This = Prev->Next; This; Prev = This, This = This->Next ) {
    if( This->Header == Header && This->End == End ) {
      Prev->Next = This->Next;
      delete This;
      This = Prev;
    }
  }
  if( Immediately ) {
    FlushFile( FixedFileName );
  }
}

wxString wxsCoder::GetCode( const wxString& FileName, const wxString& Header, const wxString& End, bool IncludeHeader, bool IncludeEnd ) {
  wxMutexLocker Lock( DataMutex );
  wxString FixedFileName = NormalizeFileName( FileName );
  FlushFile( FixedFileName );
  int TabSize = Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadInt( _T( "/tab_size" ), 4 );
  EditorManager* EM = Manager::Get()->GetEditorManager();
  assert( EM != 0 );
  M_Code_Editor* Editor = EM->GetBuiltinEditor( FixedFileName );
  if( Editor ) {
    M_Code_StyledTextCtrl* Ctrl = Editor->GetControl();
    Ctrl->SetSearchFlags( wxSCI_FIND_MATCHCASE );
    Ctrl->SetTargetStart( 0 );
    Ctrl->SetTargetEnd( Ctrl->GetLength() );
    int Position = Ctrl->SearchInTarget( Header );
    if( Position == -1 ) {
      return _T( "" );
    }
    int SpacesCut = 0;
    int SpacesPos = Position;
    while( --SpacesPos >= 0 ) {
      wxChar ch = Ctrl->GetCharAt( SpacesPos );
      if( ch == _T( '\t' ) ) {
        SpacesCut += TabSize;
      } else if( ( ch == _T( '\n' ) ) || ( ch == _T( '\r' ) ) ) {
        break;
      } else
      { SpacesCut++; }
    }
    Ctrl->SetTargetStart( Position );
    Ctrl->SetTargetEnd( Ctrl->GetLength() );
    int EndPosition = Ctrl->SearchInTarget( End );
    if( EndPosition == -1 ) {
      return _T( "" );
    }
    if( !IncludeHeader ) {
      Position += Header.Length();
    }
    if( IncludeEnd ) {
      EndPosition += End.Length();
    }
    return CutSpaces( Ctrl->GetTextRange( Position, EndPosition ), SpacesCut );
  } else {
    EncodingDetector Detector( FixedFileName );
    if( !Detector.IsOK() ) {
      return _T( "" );
    }
    wxString Content = Detector.GetWxStr();
    int Position = Content.First( Header );
    if( Position == -1 ) {
      return _T( "" );
    }
    int SpacesCut = 0;
    int SpacesPos = Position;
    while( --SpacesPos >= 0 ) {
      wxChar ch = Content.GetChar( SpacesPos );
      if( ch == _T( '\t' ) ) {
        SpacesCut += TabSize;
      } else if( ( ch == _T( '\n' ) ) || ( ch == _T( '\r' ) ) ) {
        break;
      } else
      { SpacesCut++; }
    }
    if( !IncludeHeader ) {
      Position += Header.Length();
    }
    Content.Remove( 0, Position );
    int EndPosition = Content.First( End );
    if( EndPosition == -1 ) {
      return _T( "" );
    }
    if( IncludeEnd ) {
      EndPosition += End.Length();
    }
    Content.Remove( EndPosition );
    return CutSpaces( Content, SpacesCut );
  }
}

wxString wxsCoder::GetFullCode( const wxString& FileName, wxFontEncoding& Encoding, bool &UseBOM ) {
  wxMutexLocker Lock( DataMutex );
  wxString FixedFileName = NormalizeFileName( FileName );
  FlushFile( FixedFileName );
  EditorManager* EM = Manager::Get()->GetEditorManager();
  assert( EM != 0 );
  M_Code_Editor* Editor = EM->GetBuiltinEditor( FixedFileName );
  if( Editor ) {
    Encoding = Editor->GetEncoding();
    UseBOM = Editor->GetUseBom();
    M_Code_StyledTextCtrl* Ctrl = Editor->GetControl();
    return Ctrl->GetText();
  } else {
    EncodingDetector Detector( FixedFileName );
    Encoding = Detector.GetFontEncoding();
    UseBOM = Detector.GetBOMSizeInBytes() > 0;
    return Detector.IsOK() ? Detector.GetWxStr() : _T( "" );
  }
}

void wxsCoder::PutFullCode( const wxString& FileName, const wxString& Code, wxFontEncoding Encoding, bool UseBOM ) {
  wxMutexLocker Lock( DataMutex );
  wxString FixedFileName = NormalizeFileName( FileName );
  int Index = CodeChangesFiles.Index( FixedFileName );
  if( Index != wxNOT_FOUND ) {
    for( CodeChange* Change = CodeChanges[Index]; Change; ) {
      CodeChange* Next = Change->Next;
      delete Change;
      Change = Next;
    }
    CodeChanges[Index] = 0;
  }
  EditorManager* EM = Manager::Get()->GetEditorManager();
  assert( EM != 0 );
  M_Code_Editor* Editor = EM->GetBuiltinEditor( FixedFileName );
  if( Editor ) {
    Editor->GetControl()->SetText( Code );
  } else {
    if( !cbSaveToFile( FixedFileName, Code, Encoding, UseBOM ) ) {
      Manager::Get()->GetLogManager()->Log( F( _T( "wxSmith: Couldn't write file '%s'" ), FixedFileName.wx_str() ) );
    }
  }
}

void wxsCoder::FlushFile( const wxString& FileName ) {
  int Index = CodeChangesFiles.Index( FileName );
  if( Index == wxNOT_FOUND ) {
    return;
  }
  CodeChange* Changes = CodeChanges[Index];
  if( !Changes ) {
    return;
  }
  EditorManager* EM = Manager::Get()->GetEditorManager();
  assert( EM != 0 );
  M_Code_Editor* Editor = EM->GetBuiltinEditor( FileName );
  if( Editor ) {
    wxString EOL;
    while( Changes ) {
      CodeChange* Next = Changes->Next;
      ApplyChangesEditor( Editor, Changes->Header, Changes->End, Changes->Code, Changes->CodeHasHeader, Changes->CodeHasEnd, EOL );
      delete Changes;
      Changes = Next;
    }
  } else {
    wxString EOL;
    bool HasChanged = false;
    EncodingDetector Detector( FileName );
    if( !Detector.IsOK() ) {
      Manager::Get()->GetLogManager()->Log( F( _T( "wxSmith: Couldn't open and properly read file '%s'" ), FileName.wx_str() ) );
      return;
    }
    wxString Content = Detector.GetWxStr();
    while( Changes ) {
      CodeChange* Next = Changes->Next;
      ApplyChangesString( Content, Changes->Header, Changes->End, Changes->Code, Changes->CodeHasHeader, Changes->CodeHasEnd, HasChanged, EOL );
      delete Changes;
      Changes = Next;
    }
    if( HasChanged ) {
      if( !cbSaveToFile( FileName, Content, Detector.GetFontEncoding(), Detector.GetBOMSizeInBytes() > 0 ) ) {
        Manager::Get()->GetLogManager()->Log( F( _T( "wxSmith: Couldn't write data to file '%s'" ), FileName.wx_str() ) );
      } else {
        CodeBlocksEvent event( cbEVT_PROJECT_FILE_CHANGED );
        event.SetString( FileName );
        Manager::Get()->GetPluginManager()->NotifyPlugins( event );
      }
    }
  }
  CodeChanges[Index] = 0;
}

bool wxsCoder::ApplyChangesEditor( M_Code_Editor* Editor, const wxString& Header, const wxString& End, wxString& Code, bool CodeHasHeader, bool CodeHasEnd, wxString& EOL ) {
  M_Code_StyledTextCtrl* Ctrl = Editor->GetControl();
  int FullLength = Ctrl->GetLength();
  if( EOL.IsEmpty() ) {
    for( int i = 0; i < FullLength; i++ ) {
      wxChar ch = Ctrl->GetCharAt( i );
      if( ch == _T( '\n' ) || ch == _T( '\r' ) ) {
        EOL = ch;
        if( ++i < FullLength ) {
          wxChar ch2 = Ctrl->GetCharAt( i );
          if( ( ch2 == _T( '\n' ) || ch2 == _T( '\r' ) ) && ch != ch2 ) {
            EOL.Append( ch2 );
          }
        }
        break;
      }
    }
  }
  Ctrl->SetSearchFlags( wxSCI_FIND_MATCHCASE );
  Ctrl->SetTargetStart( 0 );
  Ctrl->SetTargetEnd( FullLength );
  int Position = Ctrl->SearchInTarget( Header );
  if( Position == -1 ) {
    Manager::Get()->GetLogManager()->DebugLog( F( _T( "wxSmith: Couldn't find code with header:\n\t\"%s\"\nin file '%s'" ),
        Header.wx_str(),
        Editor->GetFilename().wx_str() ) );
    return false;
  }
  Ctrl->SetTargetStart( Position );
  Ctrl->SetTargetEnd( FullLength );
  int EndPosition = Ctrl->SearchInTarget( End );
  if( EndPosition == -1 ) {
    Manager::Get()->GetLogManager()->DebugLog( F( _T( "wxSmith: Unfinished block of auto-generated code with header:\n\t\"%s\"\nin file '%s'" ),
        Header.wx_str(),
        Editor->GetFilename().wx_str() ) );
    return false;
  }
  wxString BaseIndentation;
  int IndentPos = Position;
  while( --IndentPos >= 0 ) {
    wxChar ch = Ctrl->GetCharAt( IndentPos );
    if( ( ch == _T( '\n' ) ) || ( ch == _T( '\r' ) ) ) {
      break;
    }
  }
  while( ++IndentPos < Position ) {
    wxChar ch = Ctrl->GetCharAt( IndentPos );
    BaseIndentation.Append(
      ( ch == _T( '\t' ) ) ? _T( '\t' ) : _T( ' ' ) );
  }
  Code = RebuildCode( BaseIndentation, Code.c_str(), ( int )Code.Length(), EOL );
  if( !CodeHasHeader ) {
    Position += Header.Length();
  }
  if( CodeHasEnd ) {
    EndPosition += End.Length();
  }
  if( Ctrl->GetTextRange( Position, EndPosition ) == Code ) {
    return true;
  }
  int lineEnd = Ctrl->LineFromPosition( EndPosition );
  for( int line = Ctrl->LineFromPosition( Position ); line <= lineEnd; line++ ) {
    Ctrl->EnsureVisible( line );
  }
  Ctrl->SetTargetStart( Position );
  Ctrl->SetTargetEnd( EndPosition );
  Ctrl->ReplaceTarget( Code );
  Editor->SetModified();
  return true;
}

bool wxsCoder::ApplyChangesString( wxString& BaseContent, const wxString& Header, const wxString& End, wxString& Code, bool CodeHasHeader, bool CodeHasEnd, bool& HasChanged, wxString& EOL ) {
  wxString Content = BaseContent;
  if( EOL.IsEmpty() ) {
    for( size_t i = 0; i < Content.Length(); i++ ) {
      wxChar ch = Content.GetChar( i );
      if( ch == _T( '\n' ) || ch == _T( '\r' ) ) {
        EOL = ch;
        if( ++i < Content.Length() ) {
          wxChar ch2 = Content.GetChar( i );
          if( ( ch2 == _T( '\n' ) || ch2 == _T( '\r' ) ) && ch != ch2 ) {
            EOL.Append( ch2 );
          }
        }
        break;
      }
    }
  }
  int Position = Content.First( Header );
  if( Position == -1 ) {
    Manager::Get()->GetLogManager()->DebugLog( F( _T( "wxSmith: Couldn't find code with header:\n\t\"%s\"" ), Header.wx_str() ) );
    return false;
  }
  int IndentPos = Position;
  int IndentMax = Position;
  if( !CodeHasHeader ) {
    Position += Header.Length();
  }
  wxString Result = Content.Left( Position );
  Content.Remove( 0, Position );
  int EndPosition = Content.First( End );
  if( EndPosition == -1 ) {
    Manager::Get()->GetLogManager()->DebugLog( F( _T( "wxSmith: Unfinished block of auto-generated code with header:\n\t\"%s\"" ), Header.wx_str() ) );
    return false;
  }
  if( CodeHasEnd ) {
    EndPosition += End.Length();
  }
  wxString BaseIndentation;
  while( --IndentPos >= 0 ) {
    wxChar ch = Result.GetChar( IndentPos );
    if( ( ch == _T( '\n' ) ) || ( ch == _T( '\r' ) ) ) {
      break;
    }
  }
  while( ++IndentPos < IndentMax ) {
    wxChar ch = Result.GetChar( IndentPos );
    BaseIndentation.Append(
      ( ch == _T( '\t' ) ) ? _T( '\t' ) : _T( ' ' ) );
  }
  Code = RebuildCode( BaseIndentation, Code.c_str(), Code.Length(), EOL );
  if( Content.Mid( 0, EndPosition ) == Code ) {
    return true;
  }
  HasChanged = true;
  Result += Code;
  Result += Content.Remove( 0, EndPosition );
  BaseContent = Result;
  return true;
}

wxString wxsCoder::RebuildCode( wxString& BaseIndentation, const wxChar* Code, int CodeLen, wxString& EOL ) {
  wxString Tab;
  bool UseTab = Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadBool( _T( "/use_tab" ), false );
  int TabSize = Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadInt( _T( "/tab_size" ), 4 );
  if( !UseTab ) {
    Tab.Append( _T( ' ' ), TabSize );
  }
  if( EOL.IsEmpty() ) {
    EOL = wxT( "\n" );
  }
  BaseIndentation.Prepend( EOL );
  wxString Result;
  Result.reserve( CodeLen + 10 );
  while( *Code ) {
    switch( *Code ) {
      case _T( '\n' ): {
        while( !Result.IsEmpty() &&
               ( Result.Last() == _T( ' ' ) || Result.Last() == _T( '\t' ) ) ) {
          Result.RemoveLast();
        }
        Result << BaseIndentation;
        break;
      }
      case _T( '\t' ):
        if( UseTab ) {
          Result << Tab;
          break;
        }
      default:
        Result << *Code;
    }
    Code++;
  }
  return Result;
}

wxString wxsCoder::CutSpaces( wxString Code, int Count ) {
  int TabSize = Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadInt( _T( "/tab_size" ), 4 );
  if( TabSize < 1 ) {
    TabSize = 4;
  }
  wxString Result;
  for( ;; ) {
    int PosN = Code.Find( _T( "\n" ) );
    int PosR = Code.Find( _T( "\r" ) );
    if( ( PosN < 0 ) && ( PosR < 0 ) ) {
      break;
    }
    int Pos;
    if( PosN < 0 ) {
      Pos = PosR;
    } else if( PosR < 0 ) {
      Pos = PosN;
    } else
    { Pos = ( PosN < PosR ) ? PosN : PosR; }
    Result.Append( Code.Left( Pos ) );
    Code.Remove( 0, Pos );
    while( Code.Length() ) {
      if( ( Code[0] != _T( '\n' ) ) &&
          ( Code[0] != _T( '\r' ) ) ) {
        break;
      }
      Code.Remove( 0, 1 );
    }
    int LeftSpaces = Count;
    while( Code.Length() && LeftSpaces > 0 ) {
      if( Code[0] == _T( ' ' ) ) {
        LeftSpaces--;
      } else if( Code[0] == _T( '\t' ) ) {
        LeftSpaces -= TabSize;
      } else
      { break; }
      Code.Remove( 0, 1 );
    }
    Result.Append( _T( '\n' ) );
  }
  Result.Append( Code );
  return Result;
}

wxString wxsCoder::NormalizeFileName( const wxString& FileName ) {
  wxFileName FixedNameObject( FileName );
  FixedNameObject.Normalize( wxPATH_NORM_DOTS );
  return FixedNameObject.GetFullPath();
}

void wxsCoder::Flush( int Delay ) {
  if( Delay <= 0 ) {
    FlushTimer.Stop();
    FlushAll();
  } else {
    FlushTimer.Start( Delay, true );
  }
}

void wxsCoder::FlushAll() {
  for( int i = 0; i < ( int )CodeChangesFiles.Count(); i++ ) {
    FlushFile( CodeChangesFiles[i] );
  }
  CodeChanges.Clear();
  CodeChangesFiles.Clear();
}

void wxsCoder::FlushTimerEvent( M_Code_unused wxTimerEvent& event ) {
  FlushAll();
}
